package com.excilys.jnullsafe;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

public class RuntimeType {

    private static final RuntimeType[] EMPTY_PARAMETERS = new RuntimeType[0];

    private final Class<?>             rawType;
    private final RuntimeType[]        parameters;

    private RuntimeType(Class<?> rawType, RuntimeType[] parameters) {
        if (rawType == null) {
            throw new IllegalArgumentException("rawType should not be null");
        }

        if (parameters == null) {
            throw new IllegalArgumentException("parameters should not be null");
        }

        this.rawType = rawType;
        this.parameters = parameters;
    }

    @Override
    public String toString() {

        String signature = rawType.getSimpleName();

        for (RuntimeType parameter : parameters) {
            signature += "<" + parameter.toString() + ">";
        }

        return signature;
    }

    public Class<?> getRawType() {
        return rawType;
    }

    public RuntimeType[] getParameters() {
        return parameters;
    }

    public static RuntimeType fromReturnType(Class<?> runtimeMethodOwner, Method method) {

        if (runtimeMethodOwner.getTypeParameters().length != 0) {
            throw new UnsupportedOperationException("Cannot yet operate on a class that has type parameters");
        }

        Type genericReturnType = method.getGenericReturnType();

        if (genericReturnType instanceof Class<?>) {
            return fromClass((Class<?>) genericReturnType);
        } else if (genericReturnType instanceof ParameterizedType) {
            return fromParameterizedType((ParameterizedType) genericReturnType);
        } else if (genericReturnType instanceof TypeVariable<?>) {
            return fromTypeVariable(runtimeMethodOwner, method, (TypeVariable<?>) genericReturnType);
        } else {
            throw new UnsupportedOperationException();
        }
    }

    public static RuntimeType fromTypeVariable(Class<?> runtimeMethodOwner, Method method, TypeVariable<?> typeVariable) {

        Class<?> declaringClass = method.getDeclaringClass();

        runtimeMethodOwner.getGenericInterfaces();

        ParameterizedType runtimeClass = findRuntimeClass(runtimeMethodOwner, declaringClass);

        typeVariable.getGenericDeclaration();

        return null;
    }

    private static ParameterizedType findRuntimeClass(Class<?> runtimeSubclass, Class<?> declaringClass) {

        runtimeSubclass.getGenericInterfaces();

        runtimeSubclass.getGenericSuperclass();

        return null;
    }

    public static RuntimeType fromParameterizedType(ParameterizedType paramType) {
        Type rawType = paramType.getRawType();
        Type[] actualTypeArguments = paramType.getActualTypeArguments();

        if (!(rawType instanceof Class<?>)) {
            throw new UnsupportedOperationException("rawType should be a class, how can this even happen ?");
        }

        Class<?> rawClass = (Class<?>) rawType;

        RuntimeType[] parameters = new RuntimeType[actualTypeArguments.length];

        int i = 0;
        for (Type actualTypeArgument : actualTypeArguments) {
            if (actualTypeArgument instanceof Class<?>) {
                parameters[i] = fromClass((Class<?>) actualTypeArgument);
            } else if (actualTypeArgument instanceof ParameterizedType) {
                parameters[i] = fromParameterizedType((ParameterizedType) actualTypeArgument);
            } else {
                throw new UnsupportedOperationException("Type not supported as a parameter: " + actualTypeArgument);
            }

            i++;
        }

        return new RuntimeType(rawClass, parameters);
    }

    public static RuntimeType fromClass(Class<?> runtimeClass) {
        return new RuntimeType(runtimeClass, EMPTY_PARAMETERS);
    }

}